#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/types.h>
#include <linux/ide.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

/**********************   defines   **********************/

/**********************   defines   **********************/
#define KEY_CNT       1
#define KEY_NAME    "huafvkey"

#define KEY0VALUE		0XF0	/* 按键值 		*/
#define INVAKEY			0X00	/* 无效的按键值  */

/**********************   struct defines   **********************/
struct core_key_dev{
    dev_t dev_id;           /* dev   id */
    int major;              /* major id */
    int minor;              /* minor id */
    struct cdev cdev;       /* cdev     */
    struct class *class;
    struct device *device;
    struct device_node * node;
    int key_gpio;
    atomic_t lock;          /* atomic var */
    atomic_t key_value;
};

static struct core_key_dev key_dev;

/**********************   Function prototype   **********************/


/**********************   user defines   **********************/
static int  __init huafv_key_init(void);
static void __exit huafv_key_exit(void);

static int huafv_key_open(struct inode *inode, struct file *file);
static int huafv_key_release(struct inode *inode, struct file *file);
static ssize_t huafv_key_read(struct file *file, char *buf, size_t size, loff_t *offset);
static ssize_t huafv_key_write(struct file *file, const char *buf, size_t size, loff_t *offset);

int user_huafv_key_read(struct core_key_dev *key_dev);

struct file_operations huafv_key_fops = {
    .open = huafv_key_open,
    .release = huafv_key_release,
    .read = huafv_key_read,
    .write = huafv_key_write,
};

/* module init and exit */
module_init(huafv_key_init);
module_exit(huafv_key_exit);

/* license and author */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huafv");


/**********************   Function implementation   **********************/
static int  __init huafv_key_init(void) {
    int ret = 0;
    /* 初始化原子变量 */
    atomic_set(&key_dev.key_value, INVAKEY);
    printk(KERN_INFO "huafv key - Hello World\n");
    /* register device */
    key_dev.major = 0;
    key_dev.minor = 0;

    if(key_dev.major) {
        key_dev.dev_id = MKDEV(key_dev.major, 0);
        ret = register_chrdev_region(key_dev.dev_id,  KEY_CNT, KEY_NAME);
    }else {
        ret = alloc_chrdev_region(&key_dev.dev_id, 0, KEY_CNT, KEY_NAME);
        key_dev.major = MAJOR(key_dev.dev_id);
        key_dev.minor = MINOR(key_dev.dev_id);
    }
    printk(KERN_INFO "key_dev register returns %d\n", ret);
    printk(KERN_INFO "key_dev register major %d\n", key_dev.major);
    printk(KERN_INFO "key_dev register minor %d\n", key_dev.minor);

    /* add chr dev */
    key_dev.cdev.owner = THIS_MODULE;
    cdev_init(&key_dev.cdev, &huafv_key_fops);
    cdev_add(&key_dev.cdev, key_dev.dev_id, KEY_CNT);

    /* create class */
    key_dev.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(key_dev.class)) {
        printk(KERN_WARNING "key_dev.create failed\n");
    }

    /* create device */
    key_dev.device = device_create(key_dev.class, NULL, key_dev.dev_id, NULL, KEY_NAME);
    if(IS_ERR(key_dev.device)) {
        printk(KERN_WARNING "key_dev.create failed\n");
    }

    printk(KERN_INFO "key_dev.create register returns %d\n", ret);

    /* find device node */
    key_dev.node = of_find_node_by_path("/huafvkey");

    /* get gpio */
    key_dev.key_gpio = of_get_named_gpio(key_dev.node, "key-gpios", 0);

    /* request gpio */
    ret = gpio_request(key_dev.key_gpio, "led-gpio");
    if(ret < 0) {
        printk(KERN_WARNING "key_dev.create failed free gpio next requset\n");
        gpio_free(key_dev.key_gpio);
        ret = gpio_request(key_dev.key_gpio, "led-gpios");
    }
    printk(KERN_INFO "key_dev.create gpio_request returns %d\n", ret);


    /* set gpio dir */
    ret = gpio_direction_input(key_dev.key_gpio);
    printk(KERN_INFO "key_dev.create gpio_direction returns %d\n", ret);

    /* get gpio value */
    ret = gpio_get_value(key_dev.key_gpio);
    printk(KERN_INFO "key_dev.create gpio_get_value returns %d\n", ret);

    return 0;
}
static void __exit huafv_key_exit(void) {
    gpio_free(key_dev.key_gpio);
    device_destroy(key_dev.class, key_dev.dev_id);
    class_destroy(key_dev.class);
    cdev_del(&key_dev.cdev);
    unregister_chrdev_region(key_dev.dev_id, KEY_CNT);
}


static int huafv_key_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "huafv_key_open\n");
    file->private_data = &key_dev;
    return 0;
}
static int huafv_key_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "huafv_key_release\n");
    return 0;
}
static ssize_t huafv_key_read(struct file *file, char *buf, size_t size, loff_t *offset) {
    int value = INVAKEY;
    struct core_key_dev *key_dev = (struct core_key_dev *)file->private_data;
    // printk(KERN_INFO "huafv_key_read\n");
    value = user_huafv_key_read(key_dev);
    return copy_to_user(buf, &value, sizeof(value));;
}
static ssize_t huafv_key_write(struct file *file, const char *buf, size_t size, loff_t *offset) {
    printk(KERN_INFO "huafv_key_write\n");
    return 0;
}

int user_huafv_key_read(struct core_key_dev *key_dev) {
    int value;

    if (gpio_get_value(key_dev->key_gpio) == 0) { 		/* key0 down */
        while(!gpio_get_value(key_dev->key_gpio));		/* wait button pass */
        atomic_set(&key_dev->key_value, KEY0VALUE);
    } else {
        atomic_set(&key_dev->key_value, INVAKEY);		/* none value */
    }

    value = atomic_read(&key_dev->key_value);
    return value;
}

// /home/huafv/linux/nfs/rootfs/lib/modules/4.1.15-g3dc0a4b/
